#include "bindingalgorithmhelper.h"

#include "../arc.h"
#include "../place.h"
#include "../types/multiset.h"
#include "../utils/typefactory.h"
#include "binding.h"

#include <bool.h>

#include <QtCore/QVariant>

using namespace wfnet;

BindingAlgorithmHelper* bindingAlgorithmHelper = 0;

bool delaySortHelper(const Arc* a1, const Arc* a2)
{
    return a1->getDelay() < a2->getDelay();
}

BindingAlgorithmHelper* BindingAlgorithmHelper::getInstance()
{
    if (bindingAlgorithmHelper == 0)
    {
        bindingAlgorithmHelper = new BindingAlgorithmHelper();
    }

    return bindingAlgorithmHelper;
}

void BindingAlgorithmHelper::destroy()
{
    delete bindingAlgorithmHelper;
    bindingAlgorithmHelper = 0;
}

const int BindingAlgorithmHelper::randomMultisetValue(int size) const
{
    const int N = size - 1;

    const int index = (qrand() % (N - 0 + 1)) + 0;

    return index;
}

bool BindingAlgorithmHelper::bindFreeVariable(const VariableName& var,
    const Arc* arc, Binding& binding,
    const core::BasicTime& globalClock,
    core::BasicTime& proposedGlobalClock) const
{
    bool ret = true;

    const Place* place = arc->getPlace();

    if (place->getColorSet()->isTimed())
    {
        TimedMultiset mt = *dynamic_cast<const TimedMultiset* >(
            place->getTokens());

        ret = bindFromConstraint(var, mt, binding, globalClock,
            proposedGlobalClock);
    }
    else
    {
        Multiset mt = *dynamic_cast<const Multiset* >(place->getTokens());

        ret = bindFromConstraint(var, mt, binding, globalClock,
            proposedGlobalClock);
    }    

    return ret;
}

bool BindingAlgorithmHelper::bindUnboundVariable(
    const VariableName& var, const Arc* arc,
    Binding& binding, const core::BasicTime& globalClock,
    core::BasicTime& proposedGlobalClock) const
{
    bool ret = true;

    const Place* place = arc->getPlace();

    const ColorSet* colorSet = dynamic_cast<const ColorSet* >(
        place->getColorSet());

    ret = bindRandomly(var, colorSet->typeOf(), colorSet->isTimed(), binding,
        globalClock, proposedGlobalClock);

    return ret;
}

bool BindingAlgorithmHelper::bindRandomly(const VariableName& var,
    const TypeName& type, bool timed,
    Binding& binding, const core::BasicTime& globalClock,
    core::BasicTime& proposedGlobalClock) const
{
    bool ret = false;

    if (type == Bool::NAME)
    {
        Bool b; // TODO: make it random

        b.setValue(QVariant(qrand() % 2 == 1));

        binding.addBoundVariable(var, b);

        proposedGlobalClock = globalClock;

        ret = true;
    }

    return ret;
}

template <typename T>
bool BindingAlgorithmHelper::bindFromConstraint(const VariableName& var,
    const T& ms, Binding& binding,
    const core::BasicTime& globalClock,
    core::BasicTime& proposedGlobalClock) const
{
    return bindFromConstraint(var, ms, binding, globalClock,
        proposedGlobalClock);
}

bool BindingAlgorithmHelper::bindFromConstraint(const VariableName& var,
    const Multiset& ms, Binding& binding,
    const core::BasicTime& globalClock,
    core::BasicTime& proposedGlobalClock) const
{
    bool ret = true;

    proposedGlobalClock = globalClock;

    const int placeSize = ms.size();

    if (placeSize > 0)
    {
        const int index = BAH->randomMultisetValue(
            placeSize);

        Q_ASSERT(index < placeSize);

        ColorSet value = ms.at(index);

        binding.addBoundVariable(var, *value.getType());
    }
    else
    {
        ret = false;
    }

    return ret;
}

bool BindingAlgorithmHelper::bindFromConstraint(const VariableName& var,
    const TimedMultiset& ms, Binding& binding,
    const core::BasicTime& globalClock,
    core::BasicTime& proposedGlobalClock) const
{
    bool ret = true;

    const QList<QPair<ColorSet, BasicTime> > randomCandidates =
    ms.getSortedLowerValueList(globalClock);

    if (randomCandidates.size() > 0)
    {
        const int index = randomMultisetValue(randomCandidates.size());

        Q_ASSERT(index < randomCandidates.size());

        ColorSet value = randomCandidates.at(index).first;

        if (randomCandidates.at(index).second <= globalClock)
        {
            proposedGlobalClock = globalClock;
        }
        else
        {
            proposedGlobalClock = randomCandidates.at(index).second;
        }

        binding.addBoundVariable(var, *value.getType());
    }
    else
    {
        ColorSet value = *dynamic_cast<const ColorSet*>(
            TypeFactory::getInstance()->getTypeInstance(ms.typeOf()));
        BasicTime time;

        const bool hasUpperBound = ms.getUpperBoundValue(value, time,
            globalClock);

        if (time <= globalClock)
        {
            proposedGlobalClock = globalClock;
        }
        else
        {
            proposedGlobalClock = time;
        }

        if (hasUpperBound)
        {
           binding.addBoundVariable(var, *value.getType());
        }
        else
        {
            ret = false;
        }
    }

    return ret;
}

template <typename T>
void BindingAlgorithmHelper::getMultisetFromPlace(const Arc* arc,
    T& ms) const
{
    getMultisetFromPlace(arc, ms);
}

void BindingAlgorithmHelper::getMultisetFromPlace(
    const Arc* arc, Multiset& ms) const
{
    const Place* place = arc->getPlace();

    if (place->getColorSet()->isTimed())
    {
        Q_ASSERT(false); // timed/untimed mismatch
    }
    else
    {
        ms = *dynamic_cast<const Multiset* >(place->getTokens());
    }
}

void BindingAlgorithmHelper::getMultisetFromPlace(
    const Arc* arc, TimedMultiset& ms) const
{
    const Place* place = arc->getPlace();

    if (place->getColorSet()->isTimed())
    {
        ms = *dynamic_cast<const TimedMultiset* >(place->getTokens());
    }
    else
    {
        Q_ASSERT(false); // timed/untimed mismatch
    }
}

template <typename T>
void BindingAlgorithmHelper::getInstersection(
    const QList<const Arc* >& arcs, T& intersectedMs) const
{
    getMultisetFromPlace<T>(arcs[0], intersectedMs);

    for (uint i = arcs.size() - 1; i > 0; --i)
    {
        T mt;

        getMultisetFromPlace(arcs[i], mt);

        intersectedMs = intersectedMs.intersect(mt);
    }
}

void BindingAlgorithmHelper::setBitInBitArray(int* bitarray, int index) const
{
    const int bigtetSize = 32;

    const int bigtet = index / bigtetSize;
    const int bit = index % bigtetSize;

    int mask = bitarray[bigtet];
    mask |= 1 << bit;

    bitarray[bigtet] = mask;
}

bool BindingAlgorithmHelper::checkBitInBitArray(int* bitarray,
    int index) const
{
    const int bigtetSize = 32;

    const int bigtet = index / bigtetSize;
    const int bit = index % bigtetSize;

    const int mask = bitarray[bigtet];

    const bool ret =  (bool)(mask & (1 << bit));

    return ret;
}

void BindingAlgorithmHelper::getMinimumTime(const QList<const Arc* >& arcs,
    core::BasicTime& clock) const
{
    QList<const Arc* > _arcs = arcs;

    qSort(_arcs.begin(), _arcs.end(), delaySortHelper);

    clock.setTime(_arcs[0]->getDelay().getTime());
}

BindingAlgorithmHelper::BindingAlgorithmHelper()
{

}

BindingAlgorithmHelper::~BindingAlgorithmHelper()
{

}

template void BindingAlgorithmHelper::getInstersection<Multiset>(
    const QList<const Arc* >& arcs, Multiset& intersectedMs) const;
template void BindingAlgorithmHelper::getInstersection<TimedMultiset>(
    const QList<const Arc* >& arcs, TimedMultiset& intersectedMs) const;

template void BindingAlgorithmHelper::getMultisetFromPlace<Multiset>(
    const Arc* arc, Multiset& ms) const;
template void BindingAlgorithmHelper::getMultisetFromPlace<TimedMultiset>(
    const Arc* arc, TimedMultiset& ms) const;

template bool BindingAlgorithmHelper::bindFromConstraint<Multiset>(
    const VariableName& var, const Multiset& ms,
    Binding& binding, const core::BasicTime& globalClock,
    core::BasicTime& proposedGlobalClock) const;
template bool BindingAlgorithmHelper::bindFromConstraint<TimedMultiset>(
    const VariableName& var,
    const TimedMultiset& ms, Binding& binding,
    const core::BasicTime& globalClock,
    core::BasicTime& proposedGlobalClock) const;
